﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using NetworkInterfaceUtility;

namespace CluelessGame
{
    public class GameManager : INotifyPropertyChanged
    {
        public Player LocalPlayer;
        private int localPlayerID = 0;
        private readonly string tempName = "Player" + Guid.NewGuid();
        private Player[] players = new Player[6];
        public Player[] Players
        {
            get { return players; }
            set { players = value; NotifyPropertyChanged("Players"); }
        }

        private int _playerTurn = -1;

        public int PlayerTurn
        {
            get { return _playerTurn; }
            set { _playerTurn = value; PlayerTurnChanged(value); }
        }
        private Location originalLocationThisTurn = null;
        public bool IsLocalPlayerTurn
        {
            get { return PlayerTurn == localPlayerID; }
        }

        private CrimeCards _lastSuggestion;
        public CrimeCards LastSuggestion
        {
            get { return _lastSuggestion; }
            private set { _lastSuggestion = value; }
        }
        private int skipDisproveCount = 0;
        private bool locationLocked = false;

        public static GameObjects CurrentGameObjects = new GameObjects();

        private CardFactory cardFactory = new CardFactory();
        private bool gameStarted = false;   

        public delegate void GameObjectsChangedHandler(GameObjects gameObjects);
        public event GameObjectsChangedHandler GameObjectsChanged = delegate { };

        public delegate void PlayerTurnChangedHandler(int PlayerTurn);
        public event PlayerTurnChangedHandler PlayerTurnChanged = delegate { };

        private bool _disproving = false;
        public bool Disproving
        {
            get { return _disproving; }
            private set { _disproving = value; }
        }
        private bool _suggesting = false;
        public bool Suggesting
        {
            get { return _suggesting; }
            set { _suggesting = value; }
        }


        public GameManager(Dispatcher dispatcher)
        {
            for (int i = 0; i < Players.Length; i++)
            {
                Players[i] = new Player();
                Players[i].Name = "";
            }
           
            LocalPlayer = Players[0];
            LocalPlayer.Name = tempName;
            LocalPlayer.InGame = true;

            App.NIU.RegisterClientConnectedHandler(NIUClientConnectedHandler, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.PLAYER_NAME_CHANGE, HandlePlayerNameChangeNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.GAME_STATE, HandleGameStateNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.END_MOVE, HandleEndMoveNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.MAKE_SUGGESTION, HandleMakeSuggestionNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.SKIP_DISPROVE_SUGGESTION, HandleSkipDisproveSuggestionNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.MAKE_ACCUSATION, HandleMakeAccusationNetworkMessage, dispatcher.BeginInvoke);
            App.NIU.RegisterNetworkMessageHandler((Int32)NIUMessageIDs.MOVE_CHARACTER, HandleMoveCharacterNetworkMessage, dispatcher.BeginInvoke);
        }

        public void StartGame(int turn)
        {          
            PlayerTurn = turn;

            if (App.NIU.Hosting)
            {
                int activePlayerCount = CurrentGameObjects.ActivePlayerCount;
                CurrentGameObjects = new GameObjects();
                CurrentGameObjects.ActivePlayerCount = activePlayerCount;

                //rebuild character/player associations, reset "Lost" state
                foreach (Player player in Players)
                {
                    ReassociatePlayerWithCharacter(player);
                    player.Lost = false;
                }

                RelocateUnusedCharacters();

                List<List<Card>> cards = new List<List<Card>>();
                cardFactory.DealCards(Players, 
                    CurrentGameObjects.ActivePlayerCount, 
                    out CurrentGameObjects.Hands,
                    out CurrentGameObjects.CurrentCrimeCards);
                
                GameObjectsChanged(CurrentGameObjects);
               
                GameStateMessage message = new GameStateMessage((int)NIUMessageIDs.GAME_STATE, Players, PlayerTurn, CurrentGameObjects);
                App.NIU.BroadcastNetworkMessage(message);
            }

            gameStarted = true;

            App.ErrorLog.LogError("Starting game! Turn: " + Players[PlayerTurn].Name);

            BeginTurn(PlayerTurn);

        }

        private void RelocateUnusedCharacters()
        {
            if (CurrentGameObjects.ColonelMustard.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.ColonelMustard, CurrentGameObjects.ColonelMustard.CurrentLocation, CurrentGameObjects.Conservatory);
            }
            if (CurrentGameObjects.ProfessorPlum.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.ProfessorPlum, CurrentGameObjects.ProfessorPlum.CurrentLocation, CurrentGameObjects.Study);
            }
            if (CurrentGameObjects.MsPeakcock.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.MsPeakcock, CurrentGameObjects.MsPeakcock.CurrentLocation, CurrentGameObjects.Kitchen);
            }
            if (CurrentGameObjects.MrGreen.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.MrGreen, CurrentGameObjects.MrGreen.CurrentLocation, CurrentGameObjects.Billiardroom);
            }
            if (CurrentGameObjects.MissScarlet.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.MissScarlet, CurrentGameObjects.MissScarlet.CurrentLocation, CurrentGameObjects.Ballroom);
            }
            if (CurrentGameObjects.MsWhite.AssociatedPlayer == null)
            {
                MoveCharacter(CurrentGameObjects.MsWhite, CurrentGameObjects.MsWhite.CurrentLocation, CurrentGameObjects.Lounge);
            }
        }

        private void ReassociatePlayerWithCharacter(Player player)
        {
            if (player.AssociatedCharacter == CurrentGameObjects.MissScarlet)
            {
                player.AssociatedCharacter = CurrentGameObjects.MissScarlet;
            }
            if (player.AssociatedCharacter == CurrentGameObjects.MrGreen)
            {
                player.AssociatedCharacter = CurrentGameObjects.MrGreen;
            }
            if (player.AssociatedCharacter == CurrentGameObjects.MsPeakcock)
            {
                player.AssociatedCharacter = CurrentGameObjects.MsPeakcock;
            } 
            if (player.AssociatedCharacter == CurrentGameObjects.MsWhite)
            {
                player.AssociatedCharacter = CurrentGameObjects.MsWhite;
            } 
            if (player.AssociatedCharacter == CurrentGameObjects.ProfessorPlum)
            {
                player.AssociatedCharacter = CurrentGameObjects.ProfessorPlum;
            } 
            if (player.AssociatedCharacter == CurrentGameObjects.ColonelMustard)
            {
                player.AssociatedCharacter = CurrentGameObjects.ColonelMustard;
            }
        }

        public bool AttemptMove(Location target)
        {
            if (locationLocked)
            {
                return false;
            }
            if (LocalPlayer.AssociatedCharacter.CurrentLocation is Room)
            {
                Room currentLocation = LocalPlayer.AssociatedCharacter.CurrentLocation as Room;
                Room targetRoom = target as Room;
                Hallway targetHall = target as Hallway;

                if (targetRoom != null)
                {
                    if (currentLocation.SecretPassageConnectedRoom == targetRoom)
                    {
                        MoveCharacterLocal(LocalPlayer.AssociatedCharacter, currentLocation, target);
                        return true;
                    }
                }

                if (targetHall != null)
                {
                    if (currentLocation.AdjacentHallways.Contains(targetHall)
                        && targetHall.Occupants.Count == 0)
                    {
                        MoveCharacterLocal(LocalPlayer.AssociatedCharacter, currentLocation, target);
                        return true;
                    }
                    else if (LocalPlayer.AssociatedCharacter.CurrentLocation == CurrentGameObjects.Conservatory
                        && target == CurrentGameObjects.Lounge)
                    {
                        MoveCharacterLocal(LocalPlayer.AssociatedCharacter, currentLocation, target);
                        return true;
                    }
                    else if (LocalPlayer.AssociatedCharacter.CurrentLocation == CurrentGameObjects.Study
                        && target == CurrentGameObjects.Kitchen)
                    {
                        MoveCharacterLocal(LocalPlayer.AssociatedCharacter, currentLocation, target);
                        return true;
                    }
                }
            }
            else if (LocalPlayer.AssociatedCharacter.CurrentLocation is Hallway)
            {
                Hallway currentLocation = LocalPlayer.AssociatedCharacter.CurrentLocation as Hallway;
                Room targetLocation = target as Room;
                if (targetLocation == null)
                {
                    return false;
                }

                if (currentLocation.AdjacentRooms.Contains(targetLocation))
                {
                    MoveCharacterLocal(LocalPlayer.AssociatedCharacter, currentLocation, target);
                    return true;
                }
   
            }
            return false;
        }

        private void MoveCharacterLocal(Character character, Location oldLocation, Location newLocation)
        {
            //App.NIU.BroadcastNetworkMessage(new MoveCharacterMessage((int)NIUMessageIDs.MOVE_CHARACTER, character, oldLocation, newLocation));
            locationLocked = true;

            MoveCharacter(character, oldLocation, newLocation);
            App.NIU.BroadcastNetworkMessage(new GameStateMessage((int)NIUMessageIDs.GAME_STATE, Players, PlayerTurn, CurrentGameObjects));
        }

        private void MoveCharacter(Character character, Location oldLocation, Location newLocation)
        {
            oldLocation.Occupants.Remove(character);
            newLocation.Occupants.Add(character);
            character.CurrentLocation = newLocation;

            GameObjectsChanged(CurrentGameObjects);
        }

        public bool Accusation(Player player, CrimeCards selectedCrimeCards)
        {
            if (selectedCrimeCards.Compare(CurrentGameObjects.CurrentCrimeCards))
            {
                App.ErrorLog.LogError(String.Format("{0}'s accusacion was correct! {1} wins!",player, player));
                if (player != LocalPlayer)
                {
                    MessageBox.Show(String.Format("{0}'s accusacion was correct! {1} wins!", player, player));
                }
                return true;
            }
            else
            {
                App.ErrorLog.LogError(String.Format("{0}'s accusacion was incorrect! {1} is out of the game!", player, player));
                player.Lost = true;
                if (player != LocalPlayer)
                {
                    MessageBox.Show(String.Format("{0}'s accusacion was incorrect! {1} is out of the game!", player, player));
                }
                return false;
            }
        }

        public void RevertMove()
        {
            MoveCharacterLocal(LocalPlayer.AssociatedCharacter, LocalPlayer.AssociatedCharacter.CurrentLocation, originalLocationThisTurn);

            locationLocked = false;
        }

            
        public void MakeSuggestionLocal(CrimeCards crimeCards)
        {
            Suggesting = true;

            UpdateLocationsForSuggestion(crimeCards);

            App.NIU.BroadcastNetworkMessage(new GameStateMessage((int)NIUMessageIDs.GAME_STATE, Players, PlayerTurn, CurrentGameObjects));

 	        App.NIU.BroadcastNetworkMessage(new MakeSuggestionMessage((int)NIUMessageIDs.MAKE_SUGGESTION, crimeCards));

            locationLocked = true;
        }

        public void BeginTurn(int turn)
        {
            locationLocked = false;
            Player currentPlayer = Players[turn];
            App.ErrorLog.LogError(currentPlayer + "'s turn.");
            originalLocationThisTurn = currentPlayer.AssociatedCharacter.CurrentLocation;

            if (currentPlayer == LocalPlayer)
            {
                MessageBox.Show("Your turn!");
            } 
        }

        public void EndTurn()
        {
            App.NIU.BroadcastNetworkMessage(new EndTurnMessage((int)NIUMessageIDs.END_MOVE));
            IncrementPlayerTurn();
        }

        private void IncrementPlayerTurn()
        {
            int originalPlayerTurn = PlayerTurn;
            do
            {
                PlayerTurn = PlayerTurn + 1;
                if (PlayerTurn == CurrentGameObjects.ActivePlayerCount)
                {
                    PlayerTurn = 0;
                }
                if (PlayerTurn == originalPlayerTurn)
                {
                    //prevent infinite loop if everyone has lost.
                    break;
                }
            } while (players[PlayerTurn].Lost);
            BeginTurn(PlayerTurn);
        }


        public bool BeginDisprove(Card card)
        {
            if (card != LastSuggestion.Character
                && card != LastSuggestion.Weapon
                && card != LastSuggestion.Room)
            {
                MessageBox.Show("You must select one of the suggested cards.");
                return false;
            }

            App.NIU.BroadcastNetworkMessage(new DisproveSuggestionMessage((int)NIUMessageIDs.DISPROVE_SUGGESTION, card));
            Disproving = false;

            return true;
        }

        protected void HandlePlayerNameChangeNetworkMessage(NetworkMessageData message)
        {
            PlayerNameChangeMessage playerNameChangeMessage = message as PlayerNameChangeMessage;

            Players[playerNameChangeMessage.SourceClientID + 1].Name = playerNameChangeMessage.name;
        }

        protected void HandleGameStateNetworkMessage(NetworkMessageData message)
        {
            GameStateMessage gameStateMessage = message as GameStateMessage;

            CurrentGameObjects = gameStateMessage.gameObjects;
            Players = gameStateMessage.players;
            PlayerTurn = gameStateMessage.playerTurn;

            if (Players[localPlayerID].Name != LocalPlayer.Name)
            {
                Players[localPlayerID].Name = LocalPlayer.Name;
            }
   
            LocalPlayer = Players[localPlayerID];
            
            GameObjectsChanged(CurrentGameObjects);
        }

        protected void NIUClientConnectedHandler(Int32 clientID, ClientConnectedEvent eventType)
        {
           if (App.NIU.Hosting)
           { 
                switch (eventType)
                {
                    case ClientConnectedEvent.CLIENT_CONNECTED: 
                        Players[clientID + 1].InGame = true;
                        Players[clientID + 1].Name = "unkown";
                        CurrentGameObjects.ActivePlayerCount++;

                        App.NIU.SendNetworkMessage(new GameStateMessage((Int32)NIUMessageIDs.GAME_STATE,
                                                Players,
                                                -1,
                                                CurrentGameObjects), 
                                                clientID);
                        break;

                    case ClientConnectedEvent.CLIENT_DISCONNECTED:
                        Players[clientID + 1].InGame = false;
                        CurrentGameObjects.ActivePlayerCount--;
                        break;
                }  
            }

            if (!App.NIU.Hosting && !gameStarted)
            {
                switch (eventType)
                {
                    case ClientConnectedEvent.CLIENT_CONNECTED:
                        break;
                    case ClientConnectedEvent.CLIENT_DISCONNECTED:
                        //players = new ScrabblePlayer[maxPlayerCount];
                        //for (int i = 0; i < players.Length; i++)
                        //    players[i] = new ScrabblePlayer(i);

                        //players[0].inGame = true;
                        //players[0].name = localPlayerName;
                        //localPlayerID = 0;
                        break;
                    case ClientConnectedEvent.LOCAL_ID_SPECIFIER:
                        string currentName = LocalPlayer.Name;
                        localPlayerID = clientID + 1;
                        LocalPlayer = Players[localPlayerID];
                        LocalPlayer.Name = currentName;
                        LocalPlayer.InGame = true;
                        //sufficient
                        App.NIU.BroadcastNetworkMessage(new PlayerNameChangeMessage((Int32)NIUMessageIDs.PLAYER_NAME_CHANGE, LocalPlayer.Name));
                        break;
                }  
            }
        }

        protected void HandleEndMoveNetworkMessage(NetworkMessageData message)
        {
            IncrementPlayerTurn();
        }

        protected void HandleMakeSuggestionNetworkMessage(NetworkMessageData message)
        {
            LastSuggestion = (message as MakeSuggestionMessage).suggestion;

            //UpdateLocationsForSuggestion(lastSuggestion);

            App.ErrorLog.LogError(String.Format("{0} has suggested: {1} with the {2} in the {3}.", Players[message.SourceClientID + 1],
                LastSuggestion.Character, LastSuggestion.Weapon, LastSuggestion.Room));

            BeginDisprove(message.SourceClientID + 1);

            skipDisproveCount = 0;

            GameObjectsChanged(CurrentGameObjects);
        }

        private void UpdateLocationsForSuggestion(CrimeCards suggestion)
        {
            (suggestion.Weapon.AssociatedWeapon.CurrentLocation as Room).Weapons.Remove(suggestion.Weapon.AssociatedWeapon);
            suggestion.Room.AssociatedRoom.Weapons.Add(suggestion.Weapon.AssociatedWeapon);
            suggestion.Weapon.AssociatedWeapon.CurrentLocation = suggestion.Room.AssociatedRoom;

            (suggestion.Character.AssociatedCharacter.CurrentLocation).Occupants.Remove(suggestion.Character.AssociatedCharacter);
            suggestion.Room.AssociatedRoom.Occupants.Add(suggestion.Character.AssociatedCharacter);
            suggestion.Character.AssociatedCharacter.CurrentLocation = suggestion.Room.AssociatedRoom;

            GameObjectsChanged(CurrentGameObjects);
        }

        protected void HandleSkipDisproveSuggestionNetworkMessage(NetworkMessageData message)
        {
            SkipDisproveSuggestionMessage skipDisproveMessage = message as SkipDisproveSuggestionMessage;

            App.ErrorLog.LogError(Players[skipDisproveMessage.SourceClientID + 1].Name + " could not disprove the suggestion.");
            
            skipDisproveCount++;
            if (skipDisproveCount == CurrentGameObjects.ActivePlayerCount - 1)
            {
                skipDisproveCount = 0;
                Suggesting = false;
                App.ErrorLog.LogError("No one could disprove the suggestion!");
                MessageBox.Show("No one could disprove the suggestion!");
            }
            else
            {
                BeginDisprove(skipDisproveMessage.SourceClientID + 1);
            }
        }

        private void BeginDisprove(int sourcePlayerID)
        {
            if ((sourcePlayerID == localPlayerID - 1)
                || (localPlayerID == 0 && sourcePlayerID == CurrentGameObjects.ActivePlayerCount - 1))
            {
                if (CurrentGameObjects.Hands[LocalPlayer].Contains(LastSuggestion.Character)
                    || CurrentGameObjects.Hands[LocalPlayer].Contains(LastSuggestion.Room)
                    || CurrentGameObjects.Hands[LocalPlayer].Contains(LastSuggestion.Weapon))
                {
                    //we have a card that disproves the suggestion
                    Disproving = true;
                    MessageBox.Show("Select a card to disprove the suggestion.");
                }
                else
                {
                    //we dont have a card that disproves the suggestion
                    App.NIU.BroadcastNetworkMessage(new SkipDisproveSuggestionMessage((int)NIUMessageIDs.SKIP_DISPROVE_SUGGESTION));
                    skipDisproveCount++;
                    App.ErrorLog.LogError("You could not disprove the suggestion.");
                    if (skipDisproveCount == CurrentGameObjects.ActivePlayerCount - 1)
                    {
                        Suggesting = false;
                        skipDisproveCount = 0;
                        App.ErrorLog.LogError("No one could disprove the suggestion!");
                        MessageBox.Show("No one could disprove the suggestion!");
                    }
                }
            }
        }

        protected void HandleMakeAccusationNetworkMessage(NetworkMessageData message)
        {
            MakeAccusationMessage makeAccusationMessage = message as MakeAccusationMessage;

            Accusation(Players[makeAccusationMessage.SourceClientID + 1], makeAccusationMessage.accusation);
        }

        protected void HandleMoveCharacterNetworkMessage(NetworkMessageData message)
        {
            MoveCharacterMessage moveCharacterMessage = message as MoveCharacterMessage;

            MoveCharacter(Players[moveCharacterMessage.SourceClientID + 1].AssociatedCharacter, moveCharacterMessage.originalLocation, moveCharacterMessage.targetLocation);
        }

        public bool MoveValid(Location currentLocation, Location destination)
        {
            return true;
        }

        public bool SelectedCardValid(Card selectedCard)
        {
            return true;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }  

        #endregion
    }
}
